package net.ncraft.mc.easyframework.aop.core;

import net.ncraft.mc.easyframework.aop.AspectApplication;
import net.ncraft.mc.easyframework.aop.anno.Aspect;
import net.ncraft.mc.easyframework.aop.anno.Pointcut;
import net.ncraft.mc.easyframework.context.ApplicationContext;
import net.ncraft.mc.easyframework.context.anno.Component;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;


import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class DefaultAspectApplication implements AspectApplication {

    private final Set<Class<?>> PROXY_CLASSES;
    private final Map<Method,Set<Method>> PROXY_METHOD;
    private final Map<Class<? extends Annotation>,Method> POINTCUT_METHODS;

    private ApplicationContext context;
    public DefaultAspectApplication() {
        PROXY_CLASSES = new HashSet<>();
        PROXY_METHOD = new HashMap<>();
        POINTCUT_METHODS = new HashMap<>();
    }
    @Override
    public void scan(ApplicationContext context) {
        this.context = context;
        for (Class<?> aspectClass : context.getComponents().stream().filter(c -> c.isAnnotationPresent(Aspect.class)).collect(Collectors.toSet())) {
            for (Method method : Arrays.stream(aspectClass.getMethods()).filter(m -> m.isAnnotationPresent(Pointcut.class)).collect(Collectors.toSet())) {
                Class<? extends Annotation> enhanceAnnotation = method.getAnnotation(Pointcut.class).value();
                POINTCUT_METHODS.put(enhanceAnnotation,method);
            }
        }
        for (Class<?> component : context.getComponents()) {
            for (Method method : component.getMethods()) {
                for (Annotation annotation : method.getAnnotations()) {
                    if (POINTCUT_METHODS.containsKey(annotation.annotationType())) {
                        if (!PROXY_METHOD.containsKey(method)) {
                            PROXY_METHOD.put(method,new HashSet<>());
                        }
                        PROXY_METHOD.get(method).add(POINTCUT_METHODS.get(annotation.annotationType()));
                        PROXY_CLASSES.add(component);
                    }
                }
            }
        }
    }

    @Override
    public void pointcut() {
        for (Class<?> proxyClass : PROXY_CLASSES) {
            Set<Object> ASPECT_INSTANCES = new HashSet<>();
            Set<Method> ENHANCE_METHODS = new HashSet<>();
            Map<Method,Set<Method>> METHOD_MAP = new HashMap<>();

            for (Method method : proxyClass.getMethods()) {
                if (PROXY_METHOD.containsKey(method)) {
                    ENHANCE_METHODS.add(method);
                    METHOD_MAP.put(method,PROXY_METHOD.get(method));
                    for (Method aspectMethod : PROXY_METHOD.get(method)) {
                        ASPECT_INSTANCES.add(context.getBean(aspectMethod.getDeclaringClass()));
                    }
                }
            }

            MethodHandler methodHandler = new MethodHandler(context.getBean(proxyClass),ASPECT_INSTANCES,ENHANCE_METHODS,METHOD_MAP);
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(proxyClass);
            enhancer.setCallback(methodHandler);
            Object proxy = enhancer.create();
            context.updateBean(proxyClass,proxy);
            context.reAutowire();
        }
    }
}
